using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
using System.Threading;

using Microsoft.VisualStudio.CommandBars;

using EnvDTE;
using EnvDTE80;
using Extensibility;

using Bordecal.ImportsSorter.Configuration;
using Bordecal.ImportsSorter.Properties;

namespace Bordecal.ImportsSorter
{
	/// <summary>
	/// Class for providing add-in connectivity to the host DTE.
	/// </summary>
	[CLSCompliant(false)]
	[ComVisible(true)]
	public sealed class Connect : IDTExtensibility2, IDTCommandTarget
	{
		#region Fields

		/// <summary>
		/// Reference to the host DTE.
		/// </summary>
		private DTE2 applicationObject;

		/// <summary>
		/// Reference to the current instance of this add-in.
		/// </summary>
		private AddIn addInInstance;

		/// <summary>
		/// List of commands generated by the add-in.
		/// </summary>
		private readonly IDictionary<string, Command> addinCommands = new Dictionary<string, Command>();

		/// <summary>
		/// Short version of the name of the add-in configuration command button.
		/// </summary>
		private const string shortConfigurationCommandName = "Configure";

		/// <summary>
		/// Short version of the name of the sorting command button.
		/// </summary>
		private const string shortSortingCommandName = "Sort";

		/// <summary>
		/// Prefix used for the names of all commands created by the add-in.
		/// </summary>
		private const string commandNamePrefix = "Bordecal.ImportsSorter.Connect.";

		/// <summary>
		/// Long version of the name of the add-in configuration command button.
		/// </summary>
		private const string fullConfigurationCommandName = commandNamePrefix + shortConfigurationCommandName;

		/// <summary>
		/// Long version of the name of the sorting command button.
		/// </summary>
		private const string fullSortingCommandName = commandNamePrefix + shortSortingCommandName;

		/// <summary>
		/// Timer for use during sorting operations.
		/// </summary>
		private readonly Timer timer = null;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the Connect class.
		/// </summary>
		public Connect()
		{
		}

		#endregion

		#region Methods

		/// <summary>
		/// Returns the localized name for a Visual Studio command bar (in the locale in which the DTE is being run).
		/// </summary>
		/// <remarks>
		/// If a localized name cannot be found, the base name is returned.
		/// </remarks>
		/// <param name="baseName">The base (US English) name for the command bar.</param>
		/// <returns>The localized name for a Visual Studio command bar.</returns>
		private string LocalizedCommandBarName(string baseName)
		{
			string retVal = null;
			try
			{
				ResourceManager resourceManager = new ResourceManager("Bordecal.ImportsSorter.CommandBar", this.GetType().Assembly);
				CultureInfo cultureInfo = new CultureInfo(this.applicationObject.LocaleID);
				string resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, baseName);
				retVal = resourceManager.GetString(resourceName);
			}
			catch (MissingManifestResourceException)
			{
				retVal = baseName;
			}

			if (retVal == null)
				retVal = baseName;

			return retVal;
		}

		/// <summary>
		/// Adds the add-in configuration command button to the Tools menu.
		/// </summary>
		private void AddConfigurationCommand()
		{
			if (!this.addinCommands.ContainsKey(fullConfigurationCommandName))
			{
				string toolsMenuName = this.LocalizedCommandBarName("Tools");
				object[] contextGUIDs = new object[0];

				Commands2 commands = (Commands2)this.applicationObject.Commands;

				Command command = commands.AddNamedCommand2(this.addInInstance, shortConfigurationCommandName,
						Resources.ConfigurationCommandText, Resources.ConfigurationCommandDescription,
						true, 0, ref contextGUIDs,
						(int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
						(int)vsCommandStyle.vsCommandStyleText,
						vsCommandControlType.vsCommandControlTypeButton);

				CommandBar menuBarCommandBar = ((CommandBars)this.applicationObject.CommandBars)["MenuBar"];
				CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
				CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

				command.AddControl(toolsPopup.CommandBar, toolsPopup.Controls.Count + 1);
			}
		}

		/// <summary>
		/// Adds the sorting command button to the code window shortcut menu.
		/// </summary>
		private void AddSortingCommand()
		{
			if (!this.addinCommands.ContainsKey(fullSortingCommandName))
			{
				string codeWindowMenuName = this.LocalizedCommandBarName("Code Window");
				object[] contextGUIDs = new object[0];

				Commands2 commands = (Commands2)this.applicationObject.Commands;

				Command command = commands.AddNamedCommand2(this.addInInstance, shortSortingCommandName,
					TemplatedResources.SortingCommandText(LanguageType.Unknown),
					TemplatedResources.SortingCommandDescription(LanguageType.Unknown),
					true, 0, ref contextGUIDs,
					(int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
					(int)vsCommandStyle.vsCommandStyleText,
					vsCommandControlType.vsCommandControlTypeButton);

				CommandBar codeWindowCommandBar = ((CommandBars)this.applicationObject.CommandBars)[codeWindowMenuName];

				CommandBarControl newControl = (CommandBarControl)command.AddControl(codeWindowCommandBar, codeWindowCommandBar.Controls.Count);
				newControl.BeginGroup = true;
			}
		}

		/// <summary>
		/// Removes the commands added by the add-in from the DTE commands collection.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void RemoveCommands()
		{
			try
			{
				foreach (Command command in this.addinCommands.Values)
				{
					command.Delete();
				}
			}
			finally
			{
				this.addinCommands.Clear();
			}
		}

		/// <summary>
		/// Determines whether the code language of the IDE active document is supported by the add-in.
		/// </summary>
		/// <returns>A boolean value indicating whether the code language is supported.</returns>
		private bool ActiveDocumentLanguageSupported()
		{
			bool retVal = false;

			LanguageType languageType;
			try
			{
				languageType = CodeLanguage.LanguageTypeFromName(this.applicationObject.ActiveDocument.Language);
			}
			catch (NullReferenceException)
			{
				languageType = LanguageType.Unknown;
			}

			switch (languageType)
			{
				case LanguageType.CSharp:
				case LanguageType.VisualBasic:
				case LanguageType.JSharp:
					retVal = true;
					break;
			}

			return retVal;
		}

		#endregion

		#region IDTExtensibility2 Members

		/// <summary>
		/// Adds the configuration and sorting command bar buttons when the add-in is loaded in the IDE.
		/// </summary>
		/// <param name="Application">A reference to the host instance of the IDE.</param>
		/// <param name="ConnectMode">An ext_ConnectMode enumeration value that indicates the way the add-in was loaded into Visual Studio.</param>
		/// <param name="AddInInst">A reference to the current instance of the add-in.</param>
		/// <param name="custom">An array that may be used to pass host-specific data for use in the add-in.</param>
		public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
		{
			if (Application == null)
				throw new ArgumentNullException("Application");

			if (AddInInst == null)
				throw new ArgumentNullException("AddInInst");

			this.applicationObject = (DTE2)Application;
			this.addInInstance = (AddIn)AddInInst;

			CultureInfo hostCulture = new CultureInfo(this.applicationObject.LocaleID);
			Resources.Culture = hostCulture;

			System.Threading.Thread currentThread = System.Threading.Thread.CurrentThread;
			currentThread.CurrentCulture = hostCulture;
			currentThread.CurrentUICulture = hostCulture;

			this.AddCommands();
		}

		/// <summary>
		/// Adds command bar commands defined by the add-in.
		/// </summary>
		private void AddCommands()
		{
			if (this.addinCommands.Count == 0)
			{
				Commands2 commands = (Commands2)this.applicationObject.Commands;
				foreach (Command command in commands)
				{
					if ((command.Name != null) &&
						(command.Name.StartsWith(commandNamePrefix,
						StringComparison.OrdinalIgnoreCase)))
					{
						this.addinCommands.Add(command.Name, command);
					}
				}
			}

			this.AddConfigurationCommand();
			this.AddSortingCommand();
		}

		/// <summary>
		/// Removes the configuration and sorting command bar buttons when the add-in is unloaded from the IDE.
		/// </summary>
		/// <param name="RemoveMode">An ext_DisconnectMode enumeration value that indicates why the add-in was unloaded.</param>
		/// <param name="custom">An array that may be used to pass host-specific data for use in the add-in.</param>
		public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)
		{
			switch (RemoveMode)
			{
				case ext_DisconnectMode.ext_dm_UserClosed:
					this.RemoveCommands();
					break;
			}
		}

		/// <summary>
		/// Handler triggered when an add-in is loaded or unloaded from the IDE. 
		/// </summary>
		/// <param name="custom">An array that may be used to pass host-specific data for use in the add-in.</param>
		public void OnAddInsUpdate(ref Array custom)
		{
		}

		/// <summary>
		/// Handler triggered when the IDE begins shutdown while and add-in is running.
		/// </summary>
		/// <param name="custom">An array that may be used to pass host-specific data for use in the add-in.</param>
		public void OnBeginShutdown(ref Array custom)
		{
		}

		/// <summary>
		/// Handler triggered when an add-in set to load at IDE startup is loaded.
		/// </summary>
		/// <param name="custom">An array that may be used to pass host-specific data for use in the add-in.</param>
		public void OnStartupComplete(ref Array custom)
		{
			this.AddCommands();
		}

		#endregion

		#region IDTCommandTarget Members

		/// <summary>
		/// Returns the current status (enabled, disabled, hidden, and so forth) and/or text labelling of the specified named command. 
		/// </summary>
		/// <param name="CmdName">The name of the command to check.</param>
		/// <param name="NeededText">A vsCommandStatusTextWanted constant specifying what type of information is to be returned from the check.</param>
		/// <param name="StatusOption">A vsCommandStatus specifying the current status of the command.</param>
		/// <param name="CommandText">The text provided for the NeededText value type, if applicable.</param>
		[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		public void QueryStatus(string CmdName, vsCommandStatusTextWanted NeededText, ref vsCommandStatus StatusOption, ref object CommandText)
		{
			switch (NeededText)
			{
				case vsCommandStatusTextWanted.vsCommandStatusTextWantedNone:
					switch (CmdName)
					{
						case fullConfigurationCommandName:
							StatusOption = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
							break;
						case fullSortingCommandName:
							if (this.ActiveDocumentLanguageSupported())
							{
								StatusOption = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
							}
							else
							{
								StatusOption = vsCommandStatus.vsCommandStatusInvisible;
							}
							break;
					}
					break;
				case vsCommandStatusTextWanted.vsCommandStatusTextWantedName:
				case vsCommandStatusTextWanted.vsCommandStatusTextWantedStatus:
					if (CmdName == fullSortingCommandName)
					{
						LanguageType language = LanguageType.Unknown;
						try
						{
							language = CodeLanguage.LanguageTypeFromName(this.applicationObject.ActiveDocument.Language);
						}
						catch
						{
							language = LanguageType.Unknown;
						}

						if (NeededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedName)
						{
							CommandText = TemplatedResources.SortingCommandText(language);
						}
						else
						{
							CommandText = TemplatedResources.SortingCommandDescription(language);
						}

						if (this.ActiveDocumentLanguageSupported())
						{
							StatusOption = vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
						}
						else
						{
							StatusOption = vsCommandStatus.vsCommandStatusInvisible;
						}
					}
					break;
			}
		}

		/// <summary>
		/// Handler for execution of the specified named command. 
		/// </summary>
		/// <param name="CmdName">The name of the command to execute.</param>
		/// <param name="ExecuteOption">A vsCommandExecOption constant specifying the execution option.</param>
		/// <param name="VariantIn">A value passed to the command.</param>
		/// <param name="VariantOut">A value passed back to the invoker Exec method after the command executes.</param>
		/// <param name="Handled">A boolean value indicating whether the command was implemented.</param>
		public void Exec(string CmdName, vsCommandExecOption ExecuteOption, ref object VariantIn, ref object VariantOut, ref bool Handled)
		{
			Handled = false;
			if (ExecuteOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
			{
				switch (CmdName)
				{
					case fullConfigurationCommandName:
						using (SortingConfigurationForm configurationForm = new SortingConfigurationForm())
						{
							configurationForm.ShowDialog();
						}
						Handled = true;
						break;
					case fullSortingCommandName:
						if (this.ActiveDocumentLanguageSupported())
						{
							Settings userSettings = Settings.Default;
							new FileImportsBlockSorter().SortImportsBlock(this.applicationObject.ActiveDocument, userSettings.SortingGroupsArray, userSettings.InterGroupLineCount, this.timer);

							Handled = true;
						}
						break;
				}
			}
		}

		#endregion
	}
}